﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Trentacular.SharePoint.StsAdmin.Util;

namespace Trentacular.SharePoint.StsAdmin
{
    public class JobDefinitionExecutor : BaseConsoleHelper
    {
        public int JobExecutionTimeoutSeconds { get; set; }

        // Default JobExecutionTimeoutSeconds to 5 minutes
        public JobDefinitionExecutor() : this(300) { }

        public JobDefinitionExecutor(int jobExecutionTimeoutSeconds)
        {
            JobExecutionTimeoutSeconds = jobExecutionTimeoutSeconds;
            FailedJobIds = new List<Guid>();
        }

        private List<Guid> FailedJobIds { get; set; }

        public void ExecuteJobDefinitions()
        {
            var executionCount = ExecuteJobDefinitions(SPFarm.Local.TimerService);
            executionCount += ExecuteJobDefinitions(SPFarm.Local.Services.GetValue<SPWebService>());

            // Now wait for running jobs to complete
            if (executionCount > 0)
                WaitForJobsToComplete();
        }

        private int ExecuteJobDefinitions(SPService service)
        {
            var oneTimeJobDefinitions = service.JobDefinitions.Cast<SPJobDefinition>()
                .Where(j => !j.IsDisabled && j.Schedule != null && j.Schedule is SPOneTimeSchedule)
                .ToList();

            var failedJobIds = service.RunningJobs.Cast<SPRunningJob>()
                .Where(j => j.Status.Is(SPRunningJobStatus.Aborted, SPRunningJobStatus.Failed))
                .Select(j => j.JobDefinitionId)
                .ToList();

            var failedJobs = oneTimeJobDefinitions
                .Where(j => failedJobIds.Contains(j.Id) && !FailedJobIds.Contains(j.Id))
                .ToList();

            if (failedJobs.Count > 0)
            {
                Console.WriteLine("The following jobs encountered failures:");
                foreach (var failedjob in failedJobs)
                {
                    Console.WriteLine(string.Format("\t{0}", failedjob.Title));
                }

                FailedJobIds.AddRange(failedJobs.Select(j => j.Id));
            }

            var jobIDs = oneTimeJobDefinitions
                .Where(j => !failedJobIds.Contains(j.Id))
                .Select(j => j.Id)
                .ToList();

            foreach (var jobId in jobIDs)
            {
                var job = service.JobDefinitions[jobId];

                DoConsoleAction(string.Format("Executing Job Definition: {0}", job.Title), delegate()
                {
                    job.Execute(Guid.Empty);
                });
            }

            return jobIDs.Count;
        }

        private void WaitForJobsToComplete()
        {
            DoConsoleAction("Waiting for jobs to complete", delegate()
            {
                var stopwatch = Stopwatch.StartNew();

                while (RunningJobsExist)
                {
                    if (stopwatch.Elapsed.Seconds > JobExecutionTimeoutSeconds)
                        throw new TimeoutException(" Job execution timed out");

                    Console.Write(".");
                    Thread.Sleep(1000);
                }

                Console.Write(" ");
            });

            ExecuteJobDefinitions();
        }

        private bool RunningJobsExist
        {
            get
            {
                var runningJobs = SPFarm.Local.TimerService.RunningJobs.Cast<SPRunningJob>().ToList();
                runningJobs.AddRange(SPFarm.Local.Services.GetValue<SPWebService>().RunningJobs.Cast<SPRunningJob>());
                return runningJobs.Count(j => j.Status.Is(SPRunningJobStatus.Initialized, SPRunningJobStatus.Scheduled)) > 0;
            }
        }
    }
}
